﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace PlaydeezGames.Common
{
    public class WeightedGenerator<GeneratedType>
    {
        public delegate GeneratedType CombinationDelegate(GeneratedType first, GeneratedType second);
        public delegate GeneratedType FromStringDelegate(string source);
        public delegate string ToStringDelegate(GeneratedType source);
        private Dictionary<GeneratedType, int> table = new Dictionary<GeneratedType, int>();
        public Dictionary<GeneratedType, int>.KeyCollection Keys
        {
            get
            {
                return table.Keys;
            }
        }
        private int total = 0;
        public int this[GeneratedType key]
        {
            get
            {
                if (table.ContainsKey(key))
                {
                    return table[key];
                }
                else
                {
                    return 0;
                }
            }
            set
            {
                if (value == 0)
                {
                    if (table.ContainsKey(key))
                    {
                        total -= table[key];
                        table.Remove(key);
                    }
                }
                else
                {
                    if (table.ContainsKey(key))
                    {
                        total -= table[key];
                        table[key] = value;
                        total += table[key];
                    }
                    else
                    {
                        table.Add(key, value);
                        total += table[key];
                    }
                }
            }
        }
        public void Clear()
        {
            table.Clear();
        }
        public WeightedGenerator()
        {
        }
        public WeightedGenerator(WeightedGenerator<GeneratedType> copyFrom)
        {
            foreach (GeneratedType key in copyFrom.table.Keys)
            {
                this[key] = copyFrom[key];
            }
        }
        public WeightedGenerator(XElement element, FromStringDelegate fromStringDelegate)
        {
            FromXElement(element, fromStringDelegate);
        }
        public GeneratedType Generate()
        {
            int roll = RandomNumberGenerator.Current.Next(total);
            foreach (GeneratedType key in table.Keys)
            {
                roll -= table[key];
                if (roll < 0) return key;
            }
            return default(GeneratedType);
        }
        private string DefaultToStringDelegate(GeneratedType source)
        {
            return source.ToString();
        }
        public XElement ToXElement(XName name)
        {
            return ToXElement(name, DefaultToStringDelegate);
        }
        const string entryXName = "entry";
        const string weightXName = "weight";
        const string valueXName = "value";
        public XElement ToXElement(XName name,ToStringDelegate toStringDelegate)
        {
            XElement result = new XElement(name);
            foreach (GeneratedType key in table.Keys)
            {
                result.Add(new XElement(entryXName,
                    new XElement(weightXName, table[key].ToString()),
                    new XElement(valueXName, toStringDelegate(key))));
            }
            return result;
        }
        private void FromXElement(XElement element, FromStringDelegate fromStringDelegate)
        {
            Clear();
            foreach (XElement entry in element.Elements(entryXName))
            {
                if (entry.Element(valueXName) != null && entry.Element(weightXName) != null)
                {
                    string valueString = entry.Element(valueXName).Value;
                    string weightString = entry.Element(weightXName).Value;
                    int weight;
                    if (int.TryParse(weightString, out weight))
                    {
                        this[fromStringDelegate(valueString)] += weight;
                    }
                }
            }
        }
        public static WeightedGenerator<GeneratedType> Combine(WeightedGenerator<GeneratedType> firstGenerator, WeightedGenerator<GeneratedType> secondGenerator,CombinationDelegate combinationDelegate)
        {
            WeightedGenerator<GeneratedType> result = new WeightedGenerator<GeneratedType>();
            foreach (GeneratedType firstKey in firstGenerator.table.Keys)
            {
                foreach (GeneratedType secondKey in secondGenerator.table.Keys)
                {
                    result[combinationDelegate(firstKey, secondKey)] += firstGenerator[firstKey] * secondGenerator[secondKey];
                }
            }
            return result;
        }
    }
}
